// ==UserScript== // @name 宝可梦点击(Poke Clicker)内核汉化脚本 // @namespace PokeClickerHelper // @version 0.10.23-b // @description 采用内核汉化形式,目前汉化范围:所有任务线、城镇名、NPC及对话 // @author DreamNya, ICEYe, iktsuarpok, 我是谁?, 顶不住了, 银☆星, TerVoid // @match http://localhost:3000/ // @match https://www.pokeclicker.com // @match https://g8hh.github.io/pokeclicker/ // @match https://pokeclicker.g8hh.com // @match https://pokeclicker.g8hh.com.cn/ // @match https://yx.g8hh.com/pokeclicker/ // @match https://dreamnya.github.io/pokeclicker/ // @icon https://scriptcat.org/api/v2/resource/image/Y3VU6C1i3QnlBewG // @grant none // @run-at document-end // @license MIT // @connect cdn.jsdelivr.net // @connect raw.githubusercontent.com // ==/UserScript== /* global TownList, QuestLine:true, Notifier, MultipleQuestsQuest, App, NPC, NPCController, GameController, ko */ //储存汉化文本 const Translation = {}; const TranslationHelper = { Translation, exporting: false }; const CoreModule = window.PokeClickerHelper ?? window.PokeClickerHelperPlus; (CoreModule ?? window).TranslationHelper = TranslationHelper; window.TranslationHelper = TranslationHelper; TranslationHelper.config = { CDN: CoreModule?.get("TranslationHelperCDN", "jsDelivr", true) ?? "jsDelivr", UpdateDelay: CoreModule?.get("TranslationHelperUpdateDelay", 30, true) ?? 30, Timeout: CoreModule?.get("TranslationHelperTimeout", 5000, true) ?? 5000, }; // 引用外部资源 // CDN-jsDelivr: https://cdn.jsdelivr.net // CDN-GitHub: https://raw.githubusercontent.com // GIT: https://github.com/DreamNya/PokeClickerHelper-Translation const CDN = { jsDelivr: "https://cdn.jsdelivr.net/gh/DreamNya/PokeClickerHelper-Translation/json/", GitHub: "https://raw.githubusercontent.com/DreamNya/PokeClickerHelper-Translation/main/json/", }; const resources = ["QuestLine", "Town", "NPC"]; const now = Date.now(); const failed = []; Notifier.notify({ title: "宝可梦点击(Poke Clicker)内核汉化脚本", message: `汉化正在加载中\n此时加载存档可能导致游戏错误\n若超过1分钟此提示仍未消失,则脚本可能运行出错`, timeout: 600000, }); for (const resource of resources) { Translation[resource] = await FetchResource(resource).catch(() => { const cache = localStorage.getItem(`PokeClickerHelper-Translation-${resource}`); if (cache) { console.log("PokeClickerHelper-Translation", "fallback获取json", resource); return JSON.parse(cache); } else { console.log("PokeClickerHelper-Translation", "all failed获取json", resource); failed.push(resource); return {}; } }); } async function FetchResource(resource, force = false) { const past = +(localStorage.getItem(`PokeClickerHelper-Translation-${resource}-lastModified`) ?? 0); if ( !force && (TranslationHelper.config.UpdateDelay < 0 || now - past <= 86400 * 1000 * TranslationHelper.config.UpdateDelay) ) { const cache = localStorage.getItem(`PokeClickerHelper-Translation-${resource}`); if (cache) { console.log("PokeClickerHelper-Translation", "从存储获取json", resource); return JSON.parse(cache); } } const url = `${CDN[TranslationHelper.config.CDN]}${resource}.json`; const response = await fetch(url, { cache: "no-store", // 超时中断 signal: AbortSignal.timeout(+TranslationHelper.config.Timeout || 10000), }); if (response.status == 200) { const json = await response.json(); console.log("PokeClickerHelper-Translation", "从CDN获取json", resource); localStorage.setItem(`PokeClickerHelper-Translation-${resource}`, JSON.stringify(json)); localStorage.setItem(`PokeClickerHelper-Translation-${resource}-lastModified`, now); return json; } else { throw new Error(); } } Translation.NPCName = Translation.NPC.NPCName ?? {}; Translation.NPCDialog = Translation.NPC.NPCDialog ?? {}; TranslationHelper._toggleRaw = ko.observable(false).extend({ boolean: null }); Object.defineProperty(TranslationHelper, "toggleRaw", { get() { return TranslationHelper._toggleRaw(); }, set(newValue) { TranslationHelper._toggleRaw(newValue); }, }); // 汉化城镇 Object.values(TownList).forEach((t) => { const name = Translation.Town[t.name]; t.displayName = name ?? t.name; }); // 修改城镇文本显示绑定 $('[data-bind="text: player.town.name"]').attr( "data-bind", "text: player.town[TranslationHelper.toggleRaw ? 'name' : 'displayName']" ); $("[data-town]").each(function () { const name = $(this).attr("data-town"); $(this).attr("data-town", Translation.Town[name] || name); }); GameController.realShowMapTooltip = GameController.showMapTooltip; GameController.showMapTooltip = function (tooltipText) { const translationTown = TranslationHelper.toggleRaw ? tooltipText : Translation.Town[tooltipText] ?? tooltipText; return this.realShowMapTooltip(translationTown); }; // 汉化任务线 QuestLine.prototype.realAddQuest = QuestLine.prototype.addQuest; QuestLine.prototype.addQuest = new Proxy(QuestLine.prototype.realAddQuest, { apply(target, questline, [quest]) { const name = questline.name; const translation = Translation.QuestLine[name]; if (translation) { const description = quest.description; const displayDescription = translation.descriptions[description]; if (displayDescription) { Object.defineProperty(quest, "description", { get: () => (TranslationHelper.exporting || TranslationHelper.toggleRaw ? description : displayDescription), }); } if (quest instanceof MultipleQuestsQuest) { quest.quests.forEach((q) => { const description = q.description; const displayDescription = translation.descriptions[description]; if (displayDescription) { Object.defineProperty(q, "description", { get: () => TranslationHelper.exporting || TranslationHelper.toggleRaw ? description : displayDescription, }); } }); } } return Reflect.apply(target, questline, [quest]); }, }); window.realQuestLine = QuestLine; QuestLine = new Proxy(window.realQuestLine, { construct(...args) { const questline = Reflect.construct(...args); const { name, description } = questline; const translation = Translation.QuestLine[name]; const displayName = translation?.name; const displayDescription = translation?.description[description]; Object.defineProperty(questline, "displayName", { get: () => (TranslationHelper.exporting || TranslationHelper.toggleRaw ? name : displayName ?? name), }); if (displayDescription) { Object.defineProperty(questline, "description", { get: () => (TranslationHelper.exporting || TranslationHelper.toggleRaw ? description : displayDescription), }); } return questline; }, }); // 修改任务线文本显示绑定 $("#questLineDisplayBody knockout[data-bind='text: $data.name']").attr( "data-bind", "text: $data[TranslationHelper.toggleRaw ? 'name' : 'displayName']" ); $("#bulletinBoardModal div.modal-body h5[data-bind='text: $data.name']").attr( "data-bind", "text: $data[TranslationHelper.toggleRaw ? 'name' : 'displayName']" ); $('#questsModalQuestLinesPane knockout.font-weight-bold.d-block[data-bind="text: $data.name"]').each(function () { this.dataset.bind = "text: $data[TranslationHelper.toggleRaw ? 'name' : 'displayName']"; }); // 汉化NPC Object.values(TownList) .flatMap((i) => i.npcs) .forEach((npc) => { if (!npc || "rawDialog" in npc) { return; } npc.displayName = Translation.NPCName[npc.name] ?? npc.name; npc.rawDialog = npc.dialog; npc.translatedDialog = npc.rawDialog?.map((d) => Translation.NPCDialog[d] ?? d); delete npc.dialog; }); Object.defineProperty(NPC.prototype, "dialog", { get() { return TranslationHelper.toggleRaw ? this.rawDialog : this.translatedDialog; }, }); // 修改NPC文本显示绑定 $("#townView button[data-bind='text: $data.name, click: () => NPCController.openDialog($data)']").each(function () { this.dataset.bind = "text: $data[TranslationHelper.toggleRaw ? 'name' : 'displayName'], click: () => NPCController.openDialog($data)"; }); $("#npc-modal h5").each(function () { this.dataset.bind = "text: $data[TranslationHelper.toggleRaw ? 'name' : 'displayName']"; }); // 导出完整json方法 TranslationHelper.ExportTranslation = {}; TranslationHelper.ExportTranslation.QuestLine = function () { TranslationHelper.exporting = true; const json = App.game.quests.questLines().reduce((obj, questline) => { const { name, description } = questline; const translation = Translation.QuestLine[name]; const subObj = {}; subObj.name = translation?.name ?? name; subObj.description = { [description]: translation?.description[description] ?? "" }; subObj.descriptions = questline.quests().reduce((d, q) => { d[q.description] = translation?.descriptions[q.description] ?? ""; if (q instanceof MultipleQuestsQuest) { q.quests.forEach((qq) => { d[qq.description] = translation?.descriptions[qq.description] ?? ""; }); } return d; }, {}); obj[name] = subObj; return obj; }, {}); TranslationHelper.exporting = false; return json; }; TranslationHelper.ExportTranslation.NPC_format = function () { const toggleRaw = TranslationHelper.toggleRaw; TranslationHelper.toggleRaw = true; const json = Object.values(TownList).reduce((obj, town) => { const npcs = town.npcs; if (npcs?.length > 0) { obj[town.name] = npcs.map((npc) => { const subObj = { name: { [npc.name]: Translation.NPCName[npc.name] ?? "" }, }; if (npc.dialog?.length > 0) { subObj.dialog = Object.fromEntries(npc.dialog.map((d) => [d, Translation.NPCDialog[d] ?? ""])); } return subObj; }); } return obj; }, {}); TranslationHelper.toggleRaw = toggleRaw; return json; }; TranslationHelper.ExportTranslation.NPC = function (override) { const NPC_format = override || this.NPC_format(); const NPCDialog = Object.assign( {}, ...Object.values(NPC_format) .flat() .map((i) => i.dialog) .filter((i) => i) ); const NPCName = Object.assign( {}, ...Object.values(NPC_format) .flat() .map((i) => i.name) .filter((i) => i) ); const json = { NPCName, NPCDialog, }; return json; }; TranslationHelper.ExportTranslation.Town = function () { const json = Object.fromEntries( Object.keys(TownList).map((townName) => { return [townName, Translation.Town[townName] ?? ""]; }) ); return json; }; TranslationHelper.ImportTranslation = async function (files) { for (const file of files) { const name = file.name; const type = name.replace(/\.json$/, ""); if (!resources.includes(type)) { Notifier.notify({ title: "宝可梦点击(Poke Clicker)内核汉化脚本", message: `导入本地汉化json失败\n不支持的文件名:${name}`, timeout: 6000000, }); continue; } await new Promise((resolve) => { const fr = new FileReader(); fr.readAsText(file); fr.addEventListener("loadend", function () { const result = JSON.parse(this.result); localStorage.setItem(`PokeClickerHelper-Translation-${type}`, JSON.stringify(result)); localStorage.setItem(`PokeClickerHelper-Translation-${type}-lastModified`, now); console.log("PokeClickerHelper-Translation", "本地导入json", type); Notifier.notify({ title: "宝可梦点击(Poke Clicker)内核汉化脚本", message: `导入本地汉化json成功\n刷新游戏后生效:${name}`, type: 1, timeout: 6000000, }); resolve(); }); }); } }; // UI (需要PokeClickerHelper) if (CoreModule) { const prefix = CoreModule.UIContainerID[0].replace("#", "").replace("Container", "") + "TranslationHelper"; CoreModule.UIDOM.push(`